iT邦幫忙

2026 iThome 鐵人賽

DAY 8
0
Software Development

Android 鐵人賽: 天命最高 - 陪伴大家一步步打造屬於自己的app系列 第 8

Kotlin Coroutine 入門:協程、CoroutineScope、SupervisorJob 與 async 非同步程式設計

  • 分享至 

  • xImage
  •  

Kotlin 在 Android 開發中最大的特色之一,就是 Coroutine(協程)。相較於 Java 傳統 Thread,不僅程式更容易閱讀,也能大幅降低 Callback Hell(回呼地獄)及執行緒管理的複雜度。

今天將透過完整範例,一次介紹:

  • CoroutineScope
  • launch
  • suspend function
  • delay
  • Dispatchers.Default
  • SupervisorJob
  • async / await
  • Kotlin File 檔案操作

為什麼需要 Coroutine?

在沒有 Coroutine 的年代,如果要執行:

  • 網路下載
  • API 呼叫
  • BLE 通訊
  • MQTT 訊息收送
  • SQLite 存取
  • 檔案讀寫

通常都需要建立新的 Thread。

概念如下:

Main Thread
├── Thread A
├── Thread B
└── Thread C

Thread 建立成本高,而且大量 Thread 會造成系統負擔。

因此 Kotlin 推出了 Coroutine(協程)。

Coroutine 並不是 Thread,而是一個可以被暫停(Suspend)、恢復(Resume)的工作,因此能在少量 Thread 上同時執行大量任務。

CoroutineScope:協程的工作範圍

所有 Coroutine 都必須依附在某個 CoroutineScope。

今天的程式首先建立第一個 Scope:

var scope1 = CoroutineScope(Dispatchers.Default)

這代表:

  • 建立一個 CoroutineScope
  • 使用 Dispatchers.Default
  • 工作交由背景執行緒池處理

之後即可透過 launch 建立 Coroutine。

scope1.launch {
}

launch:建立第一個 Coroutine

範例:

scope1.launch {
repeat(3) {
println("第一個 Scope - 協程:$it")
delay(300)
}
}

程式會輸出:

第一個 Scope - 協程:0
第一個 Scope - 協程:1
第一個 Scope - 協程:2

其中:

delay(300)

代表暫停 300 毫秒。

需要注意的是,delay() 並不會阻塞 Thread,而只是暫停目前 Coroutine,因此其他 Coroutine 仍可繼續執行。

suspend Function

另一個 Coroutine:

val job = CoroutineScope(Dispatchers.Default)
job.launch {
sFun()
}

其中:

suspend fun sFun() {
println("B")
delay(500)
println("C")
delay(500)
println("D")
delay(500)
println("E")
delay(500)
println("F")
}

suspend 函式只能在 Coroutine 中呼叫。

一般函式如果直接呼叫:

fun main() {
sFun()
}

將無法編譯。

因此 suspend 可以理解成:

「只能在 Coroutine 世界中執行的函式。」

delay 與 Thread.sleep 的差異

很多初學者容易把這兩者混淆。

Thread.sleep()

Thread.sleep(1000)

會阻塞整個 Thread。

也就是:

Thread

├── sleep

└── 全部停止

而 Coroutine:

delay(1000)

只是讓目前 Coroutine 暫停。

Thread 可以去執行其他 Coroutine。

因此 Android 官方建議:

Coroutine 一律使用 delay(),不要使用 Thread.sleep()。

主執行緒仍然持續工作

主程式:

for (count in 1..10) {
println("count = $count")
Thread.sleep(400)
}

輸出會交錯出現:

count = 1
第一個 Scope - 協程:0
count = 2
B
第一個 Scope - 協程:1
count = 3
C

代表:

  • Main Thread
  • Coroutine

正在同時執行。

這也是非同步程式設計最大的特色。

SupervisorJob:避免一個失敗拖垮全部

今天最重要的新觀念就是:

SupervisorJob。

建立方式:

scope1 = CoroutineScope(
Dispatchers.Default +
SupervisorJob()
)

接著建立兩個 Coroutine。

第一個:

scope1.launch {
delay(100)
println("Job 1")
error("job 1 fail")
}

第二個:

scope1.launch {
delay(200)
println("job 2 is ok")
}

如果沒有 SupervisorJob:

Job1 發生 Exception

Job2 被取消

加入 SupervisorJob 後:

Job1 發生 Exception

Job2 繼續執行

job 2 is ok

也就是:

兄弟工作彼此獨立。

一個 Coroutine 失敗,不會影響其他 Coroutine。

Android 開發非常常見,例如:

  • 同時下載圖片
  • 同時下載聊天室資料
  • 同時下載通知

其中圖片下載失敗,不應讓聊天室整個停止。

SupervisorJob 就是為了解決這個問題。

async:有回傳值的 Coroutine

除了 launch 之外,Coroutine 還提供:

async

例如:

val value1 = scope1.async {
delay(100)
println("async job 1")
100
}
val value2 = scope1.async {
delay(200)
println("async job 2")
200
}

與 launch 最大差別:

launch async
沒有回傳值 有回傳值
回傳 Job 回傳 Deferred

Deferred 與 await()

如果直接輸出:

println(value1)

得到的是:

DeferredCoroutine{Completed}

真正的資料必須使用:

println(value1.await())

輸出:

100

如果兩個都取得:

println(
"value1 = ${value1.await()}, value2 = ${value2.await()}"
)

輸出:

value1 = 100
value2 = 200

甚至可以直接計算:

val sum =
value1.await() +
value2.await()
println(sum)

輸出:

300

Android 常利用 async 同時下載多筆 API 資料,再一起更新畫面,大幅縮短等待時間。

Kotlin File 檔案操作

今天的範例也介紹了 Kotlin 的 File API。

建立資料夾:

val dir = File("c:\kotlin")
dir.mkdir()

建立檔案:

val file =
File("c:\kotlin\data1.txt")

寫入文字:

file.writeText("Hello Kotlin")

追加內容:

file.appendText("\nGood Morning")

讀取全部內容:

val text =
file.readText()
println(text)

逐行讀取:

val lines =
file.readLines()
for (line in lines) {
println(line)
}

也可以使用 FileWriter:

FileWriter(
"data2.txt",
false
).use {
it.write("Hello")
}

.use {} 等同 Java 的 try-with-resources,可以自動關閉檔案,避免資源洩漏。

Coroutine 在 Android 的應用

目前 Android 幾乎所有背景工作都使用 Coroutine,例如:

  • REST API
  • Firebase
  • SQLite / Room
  • BLE 藍牙
  • MQTT
  • CameraX
  • AI 推論
  • 檔案下載
  • Jetpack Compose
  • DataStore

可以說,只要會 Android,就一定會接觸 Coroutine。

本日重點整理

✅ Coroutine 比 Thread 更輕量。

✅ CoroutineScope 管理 Coroutine 的生命週期。

✅ launch 適合沒有回傳值的背景工作。

✅ suspend 函式只能在 Coroutine 中呼叫。

✅ delay() 不會阻塞 Thread。

✅ Thread.sleep() 會阻塞整個 Thread。

✅ SupervisorJob 可避免一個 Coroutine 失敗影響其他 Coroutine。

✅ async 會回傳 Deferred。

✅ await() 用來取得 async 執行完成後的結果。

✅ Kotlin File API 提供簡潔的檔案讀寫方式,適合日常開發使用。

本日程式執行流程

Main Thread

├── CoroutineScope(scope1)
│ ├── launch
│ ├── launch
│ ├── async value1
│ └── async value2

├── CoroutineScope(job)
│ └── suspend sFun()

└── Main Thread 持續執行 count=1~10

透過這個範例,我們已經學會 Kotlin Coroutine 最核心的非同步設計模式,也是 Android 現代開發不可或缺的重要基礎。

下一篇預告

下一篇將介紹 Flow 與 StateFlow,了解 Kotlin 如何透過資料流(Flow)處理連續事件,以及如何在 Android 與 Jetpack Compose 中建立響應式(Reactive)UI,讓畫面能隨資料變化自動更新。

參考資料

  • Kotlin Coroutines 官方文件:https://kotlinlang.org/docs/coroutines-overview.html
  • Android Developers - Coroutines:https://developer.android.com/kotlin/coroutines
  • kotlinx.coroutines GitHub:https://github.com/Kotlin/kotlinx.coroutines

上一篇
Kotlin 抽象類別、介面與 Object:從規格設計到單例模式
下一篇
Kotlin 協程進階與檔案處理——CoroutineScope、SupervisorJob、async 與 File I/O
系列文
Android 鐵人賽: 天命最高 - 陪伴大家一步步打造屬於自己的app13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言